<script setup>
import { computed, defineAsyncComponent, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
import { useSwitchIndex } from '../obj-detn/api/use-switch-index';
import * as storage from '@/util/storage.js';
import { RndColor } from '@/util/rnd.js';
import * as utils from 'video-action/libs/utils.js';
import VideoLoader from './components/VideoLoader.vue';
import TopBar from './components/top-bar.vue';
import { DataLabel, InitAppOf } from './index.js';
import { useOpPerms } from '../obj-detn/op-perms';
import { useKbdModal } from '../obj-detn/kbd-modal.js';
import { isNewPatternClasses, PkgListReadOf } from '@/util/index.js';
import { CustomSaveFile } from './pkg-state.js';
import { CustomJudge, CustomShowConfirmNextPkg, useJudge } from './api/use-judge';
import { useShortcutKey } from '../obj-detn/api/use-shortcut-key';
import { getAssignedFileList } from '@/net/requ.js';
import { setIndex } from './api/use-switch-index';
import { useComment } from './api/use-comment';
import { useVideo } from './api/use-video';
const KeyframePanel = defineAsyncComponent(() => import('./components/keyframe-panel.vue'));
const CanvasPanel = defineAsyncComponent(() => import('./canvas-panel.vue'));
const ActionTable = defineAsyncComponent(() => import('./action-table.vue'));

const props = defineProps(['mode']);
provide('props', props);

function useClasses(taskInfo) {
  const labelsStr = computed(() => R.propOr('', 'labels', taskInfo.value));

  /** @type {function(string): string[]} */
  const toClasses = R.ifElse(
    isNewPatternClasses,
    R.compose( R.pluck('label'), R.curryN(1, JSON.parse) ),
    R.split(','),
  );

  const classes = computed(() => toClasses(labelsStr.value));
  
  
  const labelColorDic = computed(() => (
    R.converge(R.zipObj, [
      R.identity,
      R.compose( R.times(RndColor), R.length )
    ])(classes.value)
  ));
  
  return {
    classes,
    labelColorDic,
  };
}

function main() {
  taskInfo.value = storage.readAsObj('taskInfo');

  // 避免因初始化触发 watch
  if ('search' == props.mode && searchParams.value.taskIds != taskInfo.value.id) {
    searchParams.value.taskIds = taskInfo.value.id
  }

  if (!taskInfo.value) {
    message.error('请先选择任务');
    router.push({ path: '/task' });
    return;
  }

  onMounted(() => {
    DataLabel({
      addAllowDefault, addPreCond, listenShortcutKey, addShortcutKey, handleShortcutKey,
      annotationState, stop,
      changed, saveFile, judge, judgePassQues, handleReject,
      initApp,
      classes, srcs,
      labelColorDic, taskInfo,
      kbdModalProps,
      quesList, quesIndex, quesSwitchable,
      ques, video, layers,
      OpEq, OpGte, addPerms,
      confirmModalProps, judgeModalProps, useJudgeModal, commentModalProps,
    })
    .then(() => {
      watch(
        searchParams,
        () => {
          storage.writeAsStr('ques-search', searchParams.value);
          initApp(storage.readAsObj('pkgInfo'));
        },
        { deep: true }
      );
    });
  });

  onBeforeUnmount(() => {
    removeEventListener('keydown', handleShortcutKey);
  });
}

const { addAllowDefault, addPreCond, listenShortcutKey, addShortcutKey, handleShortcutKey } = useShortcutKey();
provide('addShortcutKey', addShortcutKey);

const loading = ref(true);
provide('loading', loading);

/** @type {import('vue').Ref<string[]>} */
const srcs = ref([]);

const changed = ref(false);

const { OpEq, OpGte, addPerms } = useOpPerms(storage.read('operate') || '查看');
provide('OpGte', OpGte);

/** 页面相关状态 */
const mainState = ref({
  zoom: false,
});
provide('mainState', mainState);

/** 标注相关状态 */
const annotationState = ref({
  video: {
    src: ``,
    fps: void 0,
    frames: void 0,
    duration: void 0,
    height: void 0,
    width: void 0
  },
  cachedFrameList: [],
  keyframeList: [],
  frame: {
    left: 0,
    right: 0,
  },
  /** @type {'object' | 'region' | 'skeleton' | 'action'} */
  mode: 'object',
  actionAnnotationList: [],
  videoPlaybackRate: 1,
  priorityQueue: [],
  backendQueue: [],
});
provide('annotationState', annotationState);

watch(
  () => annotationState.value.keyframeList,
  (value) => {
    if (2 <= value.length) {
      annotationState.value.frame.left = value[0]
      annotationState.value.frame.right = value[1]
    } else if (1 == value.length) {
      annotationState.value.frame.right = value[0]
      annotationState.value.frame.left = value[0]
    } else {
      annotationState.value.frame.right = 0
      annotationState.value.frame.left = 0
    }
  }
)

/** 偏好设置 */
const preferenceState = ref({
  sensitivity: 5,
  defaultFps: 10,
  defaultFpk: 50,
  /** @type {'auto' | 'v1' | 'v2'} */
  decoder: 'auto',
  objects: true,
  regions: true,
  skeletons: true,
  actions: true,
  muted: true,
  grayscale: false,
  showPopup: true
});
provide('preferenceState', preferenceState);

const { judgeModalProps, useJudgeModal, confirmModalProps, commentModalProps } = useJudge();
provide('judgeModalProps', judgeModalProps);
provide('useJudgeModal', useJudgeModal);
provide('confirmModalProps', confirmModalProps);
provide('commentModalProps', commentModalProps);

/** 假定视频地址字段 */
const taskInfo = ref(null);
provide('taskInfo', taskInfo);

const searchParams = ref(storage.readAsObj('ques-search')
  || {
    taskIds: taskInfo.value?.id,
    labelName: '',
    cateName: '',
    orderField: 'submit_time',
    orderType: 'DESC',
    queryString: '',
    start: 1,
    limit: 10,
  }
);
provide('searchParams', searchParams);

const quesTotal = ref(Number(storage.read('quesTotal')));
provide('quesTotal', quesTotal);

const { classes, labelColorDic } = useClasses(taskInfo);
provide('classes', classes);
provide('labelColorDic', labelColorDic);

const kbdModalProps = useKbdModal();
provide('kbdModalProps', kbdModalProps);

/** @type {import('vue').Ref<ImgSegmnt.Ques[]>} */
const quesList = ref([]);
const { i: quesIndex, switchable: quesSwitchable } = useSwitchIndex(quesList);
provide('quesList', quesList);
provide('quesIndex', quesIndex);
provide('quesSwitchable', quesSwitchable);

const ques = computed(() => quesList.value[quesIndex.value]);
provide('ques', ques);

const video = computed({
  get: () => ques.value?.assignedLabels?.annotation?.video ?? {},
  set: v => {
    ques.value.assignedLabels.annotation.video = v
  }
});
provide('video', video);

const layers = computed({
  get: () => ques.value?.assignedLabels?.annotation?.actionAnnotationList ?? [],
  set: v => {
    ques.value.assignedLabels.annotation.actionAnnotationList = v
  },
});
provide('layers', layers);

const comments = computed({
  get: () => ques.value?.comments ?? [],
  set: v => ques.value.comments = v,
});
provide('comments', comments);

const labels = computed(() => R.compose( R.uniq, R.pluck('action') )(layers.value));
provide('labels', labels);

const { isPaused, isStopped, showVideoPlayer, stop, handlePlayPause, handleStop } = useVideo(annotationState, video);
provide('isPaused', isPaused);
provide('isStopped', isStopped);
provide('showVideoPlayer', showVideoPlayer);
provide('stop', stop);
provide('handlePlayPause', handlePlayPause);
provide('handleStop', handleStop);

const {
  clear: clearComments,
  del: delComment,
  findComment,
} = useComment(comments);
provide('clearComments', clearComments);
provide('delComment', delComment);
provide('findComment', findComment);

function getSearchedFileList(params) {
  return REQU(`/api/taskAssignmentPackageFile/list?${ new URLSearchParams(params) }`).then(R.path(['data', 'list']));
}

const pkgList = ref(R.compose( PkgListReadOf, R.prop('user'), storage.readAsObj )('pkgInfo'));

/** 此变量变化，带动更新 storage */
const pkgInfoInStorage = ref(storage.readAsObj('pkgInfo'));
provide('pkgInfoInStorage', pkgInfoInStorage);

const pkgIndex = computed(() => {
  const findIndex = R.findIndex(R.propEq( pkgInfoInStorage.value.pkg, 'id' ));
  return findIndex(pkgList.value);
});

const switchPkgAbleToPrev = computed(() => 0 < pkgIndex.value);
provide('switchPkgAbleToPrev', switchPkgAbleToPrev);
const switchPkgAbleToNext = computed(() => pkgIndex.value + 1 < pkgList.value.length);
provide('switchPkgAbleToNext', switchPkgAbleToNext);

const initApp = InitAppOf({
  getFileList: (
    'search' == props.mode
    ? () => getSearchedFileList(searchParams.value)
    : pkgInfo => getAssignedFileList(pkgInfo.pkg).then(R.prop('data'))
  ),
  loading,
  quesIndex,
  ques,
  annotationState,
  srcs,
  classes,
  labelColorDic,
  quesList,
});
provide('initApp', initApp);

/** @param {ImgSegmnt.Ques} ques  */
function QuesData(ques) {
  const actionAnnotationList = R.map(
    item => (
      R.compose(
        R.omit(['description']),
        R.mergeDeepRight(R.__, {
          action: classes.value.indexOf(item.action),
          startFrame: utils.time2index(ques.assignedLabels.annotation.video.fps, item.start),
          endFrame: utils.time2index(ques.assignedLabels.annotation.video.fps, item.end),
          label: item.action,
          duration: Number(utils.toFixed2(item.end - item.start))
        })
      )(item)
    ),
    ques.assignedLabels.annotation.actionAnnotationList
  );

  return {
    annotation: {
      video: ques.assignedLabels.annotation.video,
      actionAnnotationList,
    },
    config: {
      actionLabelData: classes.value.map((name, id) => ({ id, name })),
    },
  };
}
provide('QuesData', QuesData);

const saveFile = CustomSaveFile({
  cond: () => OpGte('标注') && changed.value,
  pre: ques => {
    // 保存前去掉没选动作的，再根据动作标签下标排序
    const preHandle = R.compose(
      R.sort(R.ascend(
        R.compose( R.indexOf(R.__, classes.value), R.prop('action') )
      )),
      R.filter(R.compose( R.includes(R.__, classes.value), R.prop('action') )),
    );
    ques.assignedLabels.annotation.actionAnnotationList = preHandle(ques.assignedLabels.annotation.actionAnnotationList);
  },
  Params: ques => ({
    source: R.prop(storage.read('operate') || '查看', { '标注': 1, '查看': 2, '审核': 3 }),
    auditStatus: OpGte('审核') ? ques.auditStatus : null,
    assignedFileId: ques.assignedFileId,
    assignedLabels: JSON.stringify(QuesData(ques)),
    assignedImageCategories: '',
    assignedLabelsText: R.compose( R.join(','), R.uniq, R.pluck('action') )(ques.assignedLabels.annotation.actionAnnotationList),
    assignedImageCategoriesText: '',
  }),
  Msg: R.always(`第 ${ quesIndex.value + 1 } 题保存失败`),
});
provide('saveFile', saveFile);

const showConfirmNextPkg = CustomShowConfirmNextPkg({
  confirmModalProps,
  taskInfo,
  switchable: () => switchPkgAbleToNext.value,
  toNextPkg: value => {
    pkgInfoInStorage.value = value;
    initApp(value)
  },
});

// 判断审题还是审题包
// 若审题则改变此题状态后跳到下一题
//   若跳不动提示则判断，是否在最后一包
//     若是则提示最后一包审完，只能返回任务详情
//     否则确认是否下一包
// 若审题包则改变整包题状态后，跳下一包
//   判断是否可跳下一包
//     若是则提示最后一包审完，只能返回任务详情
//     否则确认是否下一包
const judge = CustomJudge({
  pre: () => confirmModalProps.value.visible = true,
  Params: params => {
    const NamesInQues = R.ifElse(
      R.has('wholePackagePass'),
      R.compose(
        R.pick(R.__, ques.value),
        R.always(['packageId'])
      ),
      R.compose(
        R.mergeRight({ lastFile: !quesSwitchable.value.next }),
        R.pick(R.__, ques.value),
        R.always(['assignedFileId', 'packageId'])
      )
    );

    const CommentsToStr = R.ifElse( R.length, R.curryN(1, JSON.stringify), R.always('') );
    return R.compose(
      R.mergeRight({ note: CommentsToStr(comments.value) }),
      NamesInQues,
    )(params);
  },
  quesJudged: params => {
    ques.value.auditStatus = params.pass ? 1 : 2;
    !params.pass && Object.assign(ques.value, R.pick(['rejectReason', 'remark' ], params));

    if (quesSwitchable.value.next) {
      setIndex(quesIndex, 'next');
      confirmModalProps.value.visible = false;
    } else {
      saveFile(ques.value);
      showConfirmNextPkg('已审核至最后一题');
    }
  },
  pkgJudged: params => {
    quesList.value.forEach(item => (item.auditStatus = params.wholePackagePass ? 1 : 2));
    saveFile(ques.value);
    showConfirmNextPkg('该包审核完毕');
  },
});
provide('judge', judge);

const judgePassQues = () => judge({ pass: true });
const judgePassPkg = () => judge({ wholePackagePass: true, remark: '' });
provide('judgePassQues', judgePassQues);
provide('judgePassPkg', judgePassPkg);

const handleReject = () => (
  comments.value.length
  ? judge({ pass: false })
  : useJudgeModal()
);
provide('handleReject', handleReject);

main();
</script>

<template>
  <div class="video-action base-label">
    <a-spin :spinning="loading">
      <top-bar></top-bar>
      <video-loader></video-loader>
      <keyframe-panel></keyframe-panel>
      <div class="canvas-panels">
        <canvas-panel position="left"></canvas-panel>
        <canvas-panel position="right" :style="{ 'flex-grow': Number(!mainState.zoom) }"></canvas-panel>
      </div>
      <action-table></action-table>
    </a-spin>
  </div>
</template>

<style>
.primary > main:has(.video-action) { overflow-y: scroll; }

.video-action { position: relative; }

.base-label .ant-btn { padding: 0 .5em; }
.base-label .ant-btn.ant-btn-icon-only {
  padding-left: 0;
  padding-right: 0;
}

.video-action .canvas-panels {
  display: flex;
  column-gap: 1em;
}

.video-action .canvas-panels > div {
  flex: 1;
  min-width: 0;
}

.video-action .canvas-panels > div > main { position: relative; }
.video-action .canvas-panels > div > main > :where(video, canvas) {
  display: block;
  width: 100%;
}
.video-action .canvas-panels > div > main > video {
  position: absolute;
  inset: 0;
}
</style>
